# main world
# defines a cube that is constantly rotating,
# simulating a "hello world" with a cube
(class dd_world_game dd_world
	(group

	# map size
	(def int tiles_width)
	(def int tiles_height)

	# base tile that everything else copies
	(def dd_meshTexture baseTile)

	# tiles
	(def (array Tile 100) tile)
	(def int tile_amount)

	(def (array int 40) tile_mage)
	(def int tile_mage_amount)

	# camera
	(def dd_matrix camera)

	(def int current_tile)

	# type of mages
	(def dd_meshColour mageMesh)
	(def dd_meshColour mageMeshSelected)
	(def dd_meshColour mageDiamondMesh)
	(def dd_meshColour mageDiamondMeshSelected)
	(def dd_meshColour mageSelfMesh)
	(def dd_meshColour mageSelfMeshSelected)

	(def int stage)

	(def int isSolved)
	(def dd_matrix solvedCamera)

	# constructor, initialise the map, and the mages
	# initialise rotation
	(function create (group)
		(group

		# clear colour
		(dd_clearColour 0.1 0.1 0.1 1)

		# initialise the base tile
		(dd_meshTexture_create this.baseTile)
		(this.baseTile.set_primitive DD_PRIMITIVE_RECTANGLE)
		(this.baseTile.loadTexture "dd_logo.bmp")

		# init mages
		(dd_meshColour_create this.mageMesh)
		(this.mageMesh.load "mage.ply")
		(dd_meshColour_create this.mageMeshSelected)
		(this.mageMeshSelected.load "mage_selected.ply")

		(dd_meshColour_create this.mageDiamondMesh)
		(this.mageDiamondMesh.load "mage_diamond.ply")
		(dd_meshColour_create this.mageDiamondMeshSelected)
		(this.mageDiamondMeshSelected.load "mage_diamond_selected.ply")

		(dd_meshColour_create this.mageSelfMesh)
		(this.mageSelfMesh.load "mage_self.ply")
		(dd_meshColour_create this.mageSelfMeshSelected)
		(this.mageSelfMeshSelected.load "mage_self_selected.ply")

		# init first stage
		(this.initStage 0)

		# camera initialisation
		(dd_matrix_identity this.camera)
		(dd_matrix_translates this.camera 0 0 -10)

		(= this.stage 0)

		)
	)

	# update - add rotation
	(function update (group) override
		(group

		(if this.isSolved
			(dd_matrix_translatea this.camera
				(* (- (- 0 (dd_matrix_x this.solvedCamera)) (dd_matrix_x this.camera)) 0.3)
				(* (- (dd_matrix_y this.solvedCamera) (dd_matrix_y this.camera)) 0.3)
				0
			)
			# camera moves towards active tile
			(dd_matrix_translatea this.camera
				(* (- (- 0 (dd_matrix_x this.tile[this.tile_mage[this.current_tile]])) (dd_matrix_x this.camera)) 0.3)
				(* (- (- 0 (dd_matrix_y this.tile[this.tile_mage[this.current_tile]])) (dd_matrix_y this.camera)) 0.3)
				0
			)
		)

		# tiles rotate towards desired value
		(for (def int i 0) (< i this.tile_amount) (= i (+ i 1))
			(this.tile[i].update)
		)

		)
	)

	# draw
	(function draw (group) override
		(group

		# apply camera
		(glMultMatrixf this.camera)

		# draw tiles
		(for (def int i 0) (< i this.tile_amount) (= i (+ i 1))
			(group
			(this.tile[i].draw)
			)
		)

		)
	)

	# input, change tiles or apply
	(function key_input (group char key) override
		(group

		# if the puzzle is solved, change input
		(if this.isSolved
			(group
			(if (== key 'w')
				(dd_matrix_translatea this.camera 0 0 1)
			(== key 's')
				(dd_matrix_translatea this.camera 0 0 -1)
			(== key 'f')
				(this.initStage (+ this.stage 1))
			)
			)
		(== key 'd')
			(group
			(if (< this.current_tile (- this.tile_mage_amount 1))
				(this.select (+ this.current_tile 1))

				(this.select 0)
			)
			)
		(== key 'a')
			(group
			(if (> this.current_tile 0)
				(this.select (- this.current_tile 1))
			# else
				(this.select (- this.tile_mage_amount 1))
			)
			)
		(== key ' ')
			(group
			(dd_world_set dd_world_menu)
			)
		(== key 'f')
			(group
			(this.rotateTiles this.current_tile 0 0)
			)
		(== key 't') (this.initStage (+ this.stage 1))
		(== key 'v') (this.setVictory)
		)


		)
	) # input

	(function rotateTiles (group int index int instant int invert)
		(group

		# on invert, pick the previous mage
		(def int side this.tile[this.tile_mage[index]].side)
#		(if invert
#			(group
#			(= side (- side 1))
#			(if (< side 0) (= side 3))
#			)
#		)

		# make box rotation
		(if (== this.tile[this.tile_mage[index]].isMageArray[side] 2)
			(group

			# i = row
			# j = column
			(def int tileIndex this.tile_mage[index])
			(for (def int i 0) (< i 3) (= i (+ i 1))
			(for (def int j 0) (< j 3) (= j (+ j 1))
				(group

				# get the index
				(def int tempIndex (+ (- tileIndex (+ this.tiles_width 1)) (* i this.tiles_width) j))

				# if out of bounds - don't rotate
				(if (|| (< tempIndex 0) (>= tempIndex this.tile_amount))
					(group)
				# else if block is in the correct line, rotate it
				(&& (>= (+ (- tileIndex this.tiles_width) (* i this.tiles_width)) 0) (== (/ tempIndex this.tiles_width) (/ (+ (- tileIndex this.tiles_width) (* i this.tiles_width)) this.tiles_width)))
					(group
					(this.tile[tempIndex].advanceSide instant)
					)
				)
				)
			)
			)

			)

		# self rotation
		(== this.tile[this.tile_mage[index]].isMageArray[side] 1)
			(group
			(this.tile[this.tile_mage[index]].advanceSide instant)
			)
		# diamond
		(== this.tile[this.tile_mage[index]].isMageArray[side] 3)
			(group

			# i = row
			# j = column
			(def int tileIndex this.tile_mage[index])
			(for (def int i 0) (< i 3) (= i (+ i 1))
			(for (def int j 0) (< j 3) (= j (+ j 1))
				(group

				(if (|| (&& (== j 0) (== i 1)) (== j 1) (&& (== j 2) (== i 1)))
					(group

					# get the index
					(def int tempIndex (+ (- tileIndex (+ this.tiles_width 1)) (* i this.tiles_width) j))
	
					# if out of bounds - don't rotate
					(if (|| (< tempIndex 0) (>= tempIndex this.tile_amount))
						(group)
					# else if block is in the correct line, rotate it
					(&& (>= (+ (- tileIndex this.tiles_width) (* i this.tiles_width)) 0) (== (/ tempIndex this.tiles_width) (/ (+ (- tileIndex this.tiles_width) (* i this.tiles_width)) this.tiles_width)))
						(group
						(this.tile[tempIndex].advanceSide instant)
						)
					)

					)
				)

				)
			)
			)

			)
		)

		# check victory condition
		(def int hasWon 1)
		(for (def int i 0) (< i this.tile_amount) (= i (+ i 1))
			(if (> this.tile[i].side 0) (= hasWon 0))
		)
		(if (&& hasWon (== instant 0)) (this.setVictory))

		)
	) # rotateTiles

	(function initStage (group int index)
		(group

		# map index
		(= this.stage index)

		# map size
		# from 4x3 to 7x6
		(= this.tiles_width  (+ 4 (* (% this.stage 6) 0.5) (% this.stage 2)))
		(= this.tiles_height (+ 3 (* (% this.stage 6) 0.5) (% this.stage 2)))

		# calculate total amount of tiles, making sure not to exceed the max
		(= this.tile_amount (* this.tiles_width this.tiles_height))
		(if (> this.tile_amount 100) (= this.tile_amount 100))

		# initialise each tile
		(for (def int i 0) (< i this.tile_amount) (= i (+ i 1))
			(group

			(Tile_create this.tile[i])
			(dd_matrix_translates this.tile[i]
				(* (% i this.tiles_width) 1.02)
				(* (/ i this.tiles_width) -1.02)
				0
			)
			(this.tile[i].mesh[0].copy this.baseTile)
			(this.tile[i].mesh[0].set_primitive_texcoords
				(* (/ 1.0 this.tiles_width) (% i this.tiles_width))
				(* (/ 1.0 this.tiles_height) (- (- this.tiles_height 1) (/ i this.tiles_width)))
				(/ 1.0 this.tiles_width)
				(/ 1.0 this.tiles_height)
			)

			)
		)

		# mages on the map 0, `tile_amount`]
		# from 2 to 40
		(= this.tile_mage_amount (+ 2 (* (/ this.stage 6) 2) (% this.stage 6)))
		(for (def int i 0) (< i this.tile_mage_amount) (= i (+ i 1))
			(group

			(def int lowIndex)
			(= lowIndex (* (/ this.tile_amount this.tile_mage_amount) i))
			(def int highIndex)
			(= highIndex (+ (* (/ this.tile_amount this.tile_mage_amount) (+ i 1))))
			(= this.tile_mage[i] (+ lowIndex (% (* (+ i 1) this.stage) (- highIndex lowIndex))))
			#(= this.tile_mage[i] (+ (* (/ this.tile_amount this.tile_mage_amount) i) (dd_math_rand (/ this.tile_amount this.tile_mage_amount))))
			)
		)

		# configure tiles to be mages
		(for (def int i 0) (< i this.tile_mage_amount) (= i (+ i 1))
			(group

			# mage is different on each of the 4 different sides
			# they can all be the same, different shape, or nothing
			# mageSet(SQUARE, SQUARE, SELF, DIAMOND)
			(= this.tile[this.tile_mage[i]].isMageArray[0] 2)
			(= this.tile[this.tile_mage[i]].isMageArray[1] 2)
			(= this.tile[this.tile_mage[i]].isMageArray[2] 2)
			(= this.tile[this.tile_mage[i]].isMageArray[3] 2)

			# second world
			(if (>= this.stage 6)
				(group

				(if (== (% i 3) 1)
					(= this.tile[this.tile_mage[i]].isMageArray[0] 3)
				)

				)
			)

			# third world
			(if (>= this.stage 12)
				(group

				(if (== (% i 4) 1)
					(= this.tile[this.tile_mage[i]].isMageArray[2] 1)
				)

				)
			)

			(for (def int j 0) (< j 4) (= j (+ j 1))
				(group
				(if (== this.tile[this.tile_mage[i]].isMageArray[j] 1)
					(group
					(this.tile[this.tile_mage[i]].mageMesh[j].copy this.mageSelfMesh)
					(this.tile[this.tile_mage[i]].mageMeshSelected[j].copy this.mageSelfMeshSelected)
					)
				(== this.tile[this.tile_mage[i]].isMageArray[j] 2)
					(group
					(this.tile[this.tile_mage[i]].mageMesh[j].copy this.mageMesh)
					(this.tile[this.tile_mage[i]].mageMeshSelected[j].copy this.mageMeshSelected)
					)
				(== this.tile[this.tile_mage[i]].isMageArray[j] 3)
					(group
					(this.tile[this.tile_mage[i]].mageMesh[j].copy this.mageDiamondMesh)
					(this.tile[this.tile_mage[i]].mageMeshSelected[j].copy this.mageDiamondMeshSelected)
					)
				)
				)
			)

			)
		)

		# init selection and select first tile
		(= this.current_tile 0)
		(this.tile[this.tile_mage[0]].select)

		# randomly rotate the map
		(for (def int i 0) (< i 20) (= i (+ i 1))
			(this.rotateTiles (dd_math_rand this.tile_mage_amount) 1 1)
		)

		# set the solved camera
		(dd_matrix_identity this.solvedCamera)
		(dd_matrix_translates this.solvedCamera (- (/ this.tiles_width 2.0) 0.5) (- (/ this.tiles_height 2.0) 0.5) 0)

		# reset victory flag
		(= this.isSolved 0)

		)
	) # init stage

	# select a new mage tile
	(function select (group int newMageIndex)
		(group

		# deselect current selection
		(this.tile[this.tile_mage[this.current_tile]].deselect)

		# select new one
		(= this.current_tile newMageIndex)
		(this.tile[this.tile_mage[this.current_tile]].select)

		)
	)

	# run when the game has won
	(function setVictory (group)
		(group

		# deselect current selection
		(this.tile[this.tile_mage[this.current_tile]].deselect)

		# set victory flag
		(= this.isSolved 1)

		)
	) # set victory

	)
) # main world
